/**
 *
 */
package vn.smartware.co.java.net.commons;

import java.io.IOException;
import java.io.InputStream;

/**
 * <p>An unsynchronized ByteArrayInputStream alternative.</p>
 * 
 * @author nakamura
 */
public class BytesInputStream extends InputStream {

	// buffer from which to read
	private byte[] buffer;
	private int index, limit, mark;
	
	// is the stream closed ?
	private boolean closed;
	
	/**
	 * Constructor.
	 * @param data
	 */
	public BytesInputStream(byte[] data) {
		this(data, 0, data.length);
	}
	
	/**
	 * Constructor.
	 * @param data
	 * @param offset 
	 * @param length 
	 */	
	public BytesInputStream(byte[] data, int offset, int length) {
		if (data == null) {
			throw new NullPointerException();
		} else if ((offset < 0) || (offset + length > data.length) || (length < 0)) {
			throw new IndexOutOfBoundsException();
		} else {
			buffer = data;
			index = offset;
			limit = offset + length;
			mark = offset;
		}
	}
	
	/**
	 * @return int
	 * @throws IOException 
	 */
	public int read() throws IOException {
		if (closed) {
			throw new IOException("Stream closed.");
		} else if (index >= limit) {
			return -1; // EOF
		} else {
			return buffer[index ++] & 0xff;
		}
	}

	/**
	 * @param data 
	 * @param offset 
	 * @param length 
	 * @return int
	 * @throws IOException 
	 */
	public int read(byte data[], int offset, int length) throws IOException {
		if (data == null) {
			throw new NullPointerException();
		} else if ((offset < 0) || (offset + length > data.length) || (length < 0)) {
			throw new IndexOutOfBoundsException();
		} else if (closed) {
			throw new IOException("Stream closed.");
		} else if (index >= limit) {
			return -1; // EOF
		} else {
			// restrict length to available data
			if (length > limit - index) {
				length = limit - index;
			}
			
			// copy out the subarray
			System.arraycopy(buffer, index, data, offset, length);
			index += length;
			return length;
		}
	}
	
	/**
	 * @param amount 
	 * @return long
	 * @throws IOException 
	 */
	public long skip(long amount) throws IOException {
		if (closed) {
			throw new IOException("Stream closed.");
		} else if (amount <= 0) {
			return 0;
		} else {
			// restrict amount to available data
			if (amount > limit - index) {
				amount = limit - index;
			}
			
			index += (int)amount;
			return amount;
		}
	}
	
	/**
	 * @return int
	 * @throws IOException 
	 */
	public int available() throws IOException {
		if (closed) {
			throw new IOException("Stream closed.");
		} else {
			return limit - index;
		}
	}
	
	/**
	 * close
	 */
	public void close() {
		closed = true;
	}
	
	/**
	 * @param readLimit 
	 */
	public void mark(int readLimit) {
		mark = index;
	}
	
	/**
	 * @throws IOException 
	 */
	public void reset() throws IOException {
		if (closed) {
			throw new IOException("Stream closed.");
		} else {
			// reset index
			index = mark;
		}
	}
}
